Allegro data exploration

Biblioteki

In [2]:
import numpy as np
import pandas as pd
import holoviews as hv
from matplotlib import pyplot as plt
from scipy.sparse.csgraph import minimum_spanning_tree
from scipy.cluster.hierarchy import dendrogram, linkage
import json, re, datetime
import lens
import warnings
import itertools

hv.extension('bokeh')

Wczytujemy dane i ustawiamy odpowiednie typy dla poszczególnych kolumn

W pierwszym kroku eksploracji wykorzystamy narzędzia udostępnione w pakiecie pandas. Moim zdaniem wystarczają one w większości zastosowań, ze względu na wysoką kompatybilność z najpopularniejszym standardem ramek danych w pythonie - pd.DataFrame. Następnie zaprezentuje możliwości biblioteki lens i opiszę jak wyglądało jej użycie

In [3]:
allegro_transactions = pd.read_csv('allegro-api-transactions.csv')
allegro_transactions.date = allegro_transactions.date.apply(lambda str_date_time : datetime.datetime.strptime(str_date_time, '%Y-%m-%d %H:%M:%S'))
allegro_transactions.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 420020 entries, 0 to 420019
Data columns (total 14 columns):
lp                        420020 non-null int64
date                      420020 non-null datetime64[ns]
item_id                   420020 non-null int64
categories                420020 non-null object
pay_option_on_delivery    420020 non-null int64
pay_option_transfer       420020 non-null int64
seller                    420020 non-null object
price                     420020 non-null float64
it_is_allegro_standard    420020 non-null int64
it_quantity               420020 non-null int64
it_is_brand_zone          420020 non-null int64
it_seller_rating          420020 non-null int64
it_location               420020 non-null object
main_category             420020 non-null object
dtypes: datetime64[ns](1), float64(1), int64(8), object(4)
memory usage: 44.9+ MB

1. Standardowe narzędzia pandas.DataFrame

W celu zapewnienia poprawnego zachowania wbudowanych funkcji pd.DataFrame, przekształcimy kolumny na standardowe dla pandasa typy. Okazuje się, że lens nie radzi sobie z typami pd.Timestamp i pd.Categorical, co powinniśmy uznać za wadę pakietu, bo są to typy ze standardu biblioteki pandas.

Trzy podstawowe wbudowane narzędzia, z których korzysta się we wstępnej eksploracji danych to:

  • pd.DataFrame.info()
  • pd.DataFrame.describe()
  • pd.DataFrame.head()

1.1 pd.DataFrame.info()

In [4]:
pandas_compliant_allegro_transactions = allegro_transactions.copy()
pandas_compliant_allegro_transactions.item_id = pd.Categorical(pandas_compliant_allegro_transactions.item_id)
pandas_compliant_allegro_transactions.pay_option_on_delivery = pd.Categorical(pandas_compliant_allegro_transactions.pay_option_on_delivery)
pandas_compliant_allegro_transactions.pay_option_transfer = pd.Categorical(pandas_compliant_allegro_transactions.pay_option_transfer)
pandas_compliant_allegro_transactions.seller = pd.Categorical(pandas_compliant_allegro_transactions.seller)
pandas_compliant_allegro_transactions.it_is_allegro_standard = pd.Categorical(pandas_compliant_allegro_transactions.it_is_allegro_standard)
pandas_compliant_allegro_transactions.it_is_brand_zone = pd.Categorical(pandas_compliant_allegro_transactions.it_is_brand_zone)
pandas_compliant_allegro_transactions.main_category = pd.Categorical(pandas_compliant_allegro_transactions.main_category)
pandas_compliant_allegro_transactions.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 420020 entries, 0 to 420019
Data columns (total 14 columns):
lp                        420020 non-null int64
date                      420020 non-null datetime64[ns]
item_id                   420020 non-null category
categories                420020 non-null object
pay_option_on_delivery    420020 non-null category
pay_option_transfer       420020 non-null category
seller                    420020 non-null category
price                     420020 non-null float64
it_is_allegro_standard    420020 non-null category
it_quantity               420020 non-null int64
it_is_brand_zone          420020 non-null category
it_seller_rating          420020 non-null int64
it_location               420020 non-null object
main_category             420020 non-null category
dtypes: category(7), datetime64[ns](1), float64(1), int64(3), object(2)
memory usage: 43.1+ MB

Informacje, które udostępnia nam polecenie pd.DataFrame.info():

  • liczba wierszy,
  • typ każdej kolumny,
  • liczba wartości różnych od null, w każdej kolumnie,
  • liczba typów danych w ramce,
  • zużycie pamięci przez ramkę danych

Wnioski na podstawie metody info

  • Dane nie mają żadnych braków
  • Danę są w większości kategoryczne, zbiór posiada jedną kolumnę czasową, jedną zmiennoprzecinkową, 3 całkowite i 2 bliżej nieokreślone

1.2 pd.DataFrame.describe()

Metoda describe() zwraca opis kolumn naszej ramki danych. Wywołana bez żadnych parametrów opisuje tylko i wyłącznie kolumny, które ramka danych rozpoznaje jako numeryczne.

In [5]:
pandas_compliant_allegro_transactions.describe()
Out[5]:
lp price it_quantity it_seller_rating
count 420020.000000 420020.000000 420020.000000 420020.000000
mean 210009.500000 76.811350 6748.274823 20402.593496
std 121249.474369 390.326271 23387.248064 36682.898636
min 0.000000 0.000000 0.000000 -1.000000
25% 105004.750000 9.000000 6.000000 1581.000000
50% 210009.500000 24.990000 95.000000 6711.000000
75% 315014.250000 68.450000 931.000000 21007.000000
max 420019.000000 119000.000000 99999.000000 292074.000000

Taki sam wynik uzyskamy wywołując metodę describe z parametrem include=np.number

In [6]:
pandas_compliant_allegro_transactions.describe(include=np.number)
Out[6]:
lp price it_quantity it_seller_rating
count 420020.000000 420020.000000 420020.000000 420020.000000
mean 210009.500000 76.811350 6748.274823 20402.593496
std 121249.474369 390.326271 23387.248064 36682.898636
min 0.000000 0.000000 0.000000 -1.000000
25% 105004.750000 9.000000 6.000000 1581.000000
50% 210009.500000 24.990000 95.000000 6711.000000
75% 315014.250000 68.450000 931.000000 21007.000000
max 420019.000000 119000.000000 99999.000000 292074.000000

W celu uzyskania informacji o pozostałych rodzajach kolumn możemy użyć parametrów:

  • include='object'
  • include='category'
  • include='datetime'

Jeżeli chcemy, aby metoda describe() zwróciła nam wszystkie kolumny, wywołujemy ją z parametrem

  • include='all'
In [7]:
pandas_compliant_allegro_transactions.describe(include='object')
Out[7]:
categories it_location
count 420020 420020
unique 9020 10056
top ['Dom i Ogród', 'Ogród', 'Rośliny', 'Rośliny o... Warszawa
freq 3753 23244
In [8]:
pandas_compliant_allegro_transactions.describe(include='category')
Out[8]:
item_id pay_option_on_delivery pay_option_transfer seller it_is_allegro_standard it_is_brand_zone main_category
count 420020 420020 420020 420020 420020 420020 420020
unique 332519 2 2 51064 2 2 27
top 6061413744 1 1 Allegro 1 0 Dom i Ogród
freq 111 368766 341264 1640 245063 413008 91042
In [9]:
pandas_compliant_allegro_transactions.loc[:,'datetime truncated to hour']=pandas_compliant_allegro_transactions.date.apply(lambda x : pd.Timestamp(year = x.year,
                                                                         month = x.month,
                                                                         day = x.day,
                                                                         hour = x.hour))
pandas_compliant_allegro_transactions.describe(include='datetime')
Out[9]:
date datetime truncated to hour
count 420020 420020
unique 38953 24
top 2016-04-03 22:32:33 2016-04-03 20:00:00
freq 217 48574
first 2016-04-03 00:00:09 2016-04-03 00:00:00
last 2016-04-03 23:59:59 2016-04-03 23:00:00

Wnioski na podstawie metody describe

  • Wartości liczbowe są różnych rzędów wielkości, budując model na ich podstawie powinniśmy je przeskalować
  • Kolumna price może zawierać pojedyncze outliery, które wpływają na zbyt duże odchylenie standardowe
  • Kolumna categories nie jest kolumną o wartościach atomowych, ale najpopularniejszy zestaw kolumn pojawia się w ramce całkiem często
  • Bardzo informatywna jest ramka zwrócona przez describe z parametrem include='category'. Dzięki top i freq, mamy szybki ogląd na najbardziej popularną kategorię i jej częstość występowania w porównaniu do pozostałych. Widzimy, że najczęstszą kategorią jest Dom i Ogród, a najczęstszym sprzedawcą Allegro.
  • Ostatnia ramka przedstawia dane związane z czasem. Widzimy, że dane, które eksplorujemy pochodzą z 3 kwietnia 2016 roku. Oprócz tego, po nietrudnym przekształceniu widzimy też, że najwięcej transakcji zostało wykonanych o godzinie 20

1.3 pd.DataFrame.head()

In [10]:
pandas_compliant_allegro_transactions.head()
Out[10]:
lp date item_id categories pay_option_on_delivery pay_option_transfer seller price it_is_allegro_standard it_quantity it_is_brand_zone it_seller_rating it_location main_category datetime truncated to hour
0 0 2016-04-03 21:21:08 4753602474 ['Komputery', 'Dyski i napędy', 'Nośniki', 'No... 1 1 radzioch666 59.99 1 997 0 50177 Warszawa Komputery 2016-04-03 21:00:00
1 1 2016-04-03 15:35:26 4773181874 ['Odzież, Obuwie, Dodatki', 'Bielizna damska',... 1 1 InwestycjeNET 4.90 1 9288 0 12428 Warszawa Odzież, Obuwie, Dodatki 2016-04-03 15:00:00
2 2 2016-04-03 14:14:31 4781627074 ['Dom i Ogród', 'Budownictwo i Akcesoria', 'Śc... 1 1 otostyl_com 109.90 1 895 0 7389 Leszno Dom i Ogród 2016-04-03 14:00:00
3 3 2016-04-03 19:55:44 4783971474 ['Książki i Komiksy', 'Poradniki i albumy', 'Z... 1 1 Matfel1 18.50 0 971 0 15006 Wola Krzysztoporska Książki i Komiksy 2016-04-03 19:00:00
4 4 2016-04-03 18:05:54 4787908274 ['Odzież, Obuwie, Dodatki', 'Ślub i wesele', '... 1 1 PPHU_RICO 19.90 1 950 0 32975 BIAŁYSTOK Odzież, Obuwie, Dodatki 2016-04-03 18:00:00

W przypadkach, kiedy nazwa kolumny nie jest wystarczająco informatywna, warto spojrzeć do środka ramki danych, a przynajmniej na parę pierwszych wierszy. Pozwala to często zgadnąć czym jest dana wartość, nawet jeśli nie jest odpowiednio opisana.

2. pakiet lens

In [11]:
warnings.filterwarnings("ignore")
ls = lens.summarise(allegro_transactions)
explorer = lens.explore(ls)
explorer.describe()
Out[11]:
lpdateitem_idcategoriespay_option_on_deliverypay_option_transfersellerpriceit_is_allegro_standardit_quantityit_is_brand_zoneit_seller_ratingit_locationmain_category
descnumericNonenumericNonecategoricalcategoricalNonenumericcategoricalnumericcategoricalnumericNonecategorical
dtypeint64datetime64[ns]int64objectint64int64objectfloat64int64int64int64int64objectobject
notnulls420020420020420020420020420020420020420020420020420020420020420020420020420020420020
nulls00000000000000
unique42002038953332519902022510649722273052460931005627

Pakiet lens, udostępnie metodę describe() podobną do pd.DataFrame.describe(), która niestety daje mniejsze możliwości niż metoda z pakietu pandas.

Dużo bardziej interesujące są pozostałe funkcje obiektu explorer

2.1 correlation_plot()

Po analizie correlation_plot, widzimy wysoką korelację między oznaczeniem sprzedawcy jako allegro standard i jego oceną. Praktycznie nieskorelowane z jakimikolwiek innymi zmiennymi są sztuczne zmienne jak np. lp czy też zmienne identyfikacyjne jak item_id.

In [12]:
explorer.correlation_plot()

2.2 column_details()

Column details umożliwa podsumowanie zmiennych ilościowych, jak i jakościowych. Podsumowanie zmiennych ilościowych zawiera trochę więcej informacji niż pandasowy describe

In [13]:
explorer.column_details('price')
Out[13]:
price
descnumeric
dtypefloat64
min0.0
max119000.0
mean76.81135036404265
median24.99
std390.32627143155236
sum32262303.38
IQR59.45
In [14]:
explorer.column_details('main_category')
Out[14]:

desc: categorical, dtype: object

itemfrequency
Dom i Ogród91042
Odzież, Obuwie, Dodatki54257
Motoryzacja45941
Dla Dzieci42107
Uroda28096
Sport i Turystyka27532
RTV i AGD20341
Telefony i Akcesoria19805
Komputery14491
Zdrowie13166
Książki i Komiksy11572
Delikatesy8074
Gry7150
Rękodzieło6574
Kolekcje6146
Przemysł5959
Biżuteria i Zegarki5808
Biuro i Reklama3194
Fotografia2381
Muzyka1961
Antyki i Sztuka1214
Konsole i automaty1053
Filmy1005
Instrumenty617
Sprzęt estradowy, studyjny i DJ-ski413
Bilety119
Nieruchomości2

3. Analiza czasowa

Do analizy liczby transakcji w ciągu dnia, wykorzystam pakiet holoviews. Dzięki interaktywnym wykresom, mogę łatwo rozpoznać wykres poszczególnych kategorii

In [16]:
from bokeh.models import HoverTool

allegro_transactions.loc[:,'hour'] = allegro_transactions.date.apply(lambda timestamp : timestamp.time().hour)

df = allegro_transactions.groupby(['hour','main_category']).count()['lp'].unstack().fillna(0)

curve_dict = {colname:hv.Curve((np.arange(24), df.loc[:,colname]), label=colname) for colname in df.columns}

ndOverlay = hv.NdOverlay(curve_dict, kdims='kategoria')
ndOverlay.opts(title = "Liczba transakcji w ciągu dnia w podziale na kategorie",
               tools=['hover','tap'], padding=0.1, width=960, height=540, show_grid=True,
               ylabel='Liczba transakcji', xlabel='godzina')
ndOverlay.redim(x=hv.Dimension('x', range=(0, 23)),y=hv.Dimension('y',range=(0,12000)))
Out[16]:

Wnioski z analizy szeregu czasowego

Mamy podstawy twierdzić, że brakuje znacznej ilości danych z godziny 21

4. Rozkłady zmiennych i agregaty

4.1. Rozkłady zmiennych numerycznych

Widzimy po wykresach poniżej, że zmienne numeryczne przed przeskalowaniem nie nadają się do końca do użytku. Każda zmienna zawiera jakieś outliery (np. w przypadku ceny przeprowadzone zostały dwie transakcje związane ze sprzedażą nieruchomości). Po zmianie skali z jednostkowej na logarymiczną, otrzymaliśmy histogramy, które prezentują wartości o znacznie ładniejszych rozkładach.

In [30]:
fig = plt.figure(figsize = (16,16))
columns = ['price','it_quantity','it_seller_rating']
for i, colname in enumerate(columns, start=1):
    v = allegro_transactions.loc[:,colname]
    plt.subplot(3, 2, 2*i-1)
    plt.hist(v.loc[v>0], label = f'{colname} before scaling to log')
    plt.title(f'{colname} before scaling to log')
    plt.subplot(3, 2, 2*i)
    plt.hist(np.log(v.loc[v>0]), label = f'{colname} after scaling to log')
    plt.title(f'{colname} after scaling to log')

4.2 Agregat

Kolejnym krokiem w naszej eksploracji może być obliczenie agregatu związanego z różnymi kategoriami. Przykładowym i najbardziej sensownym jest agregat będący sumą wartości transakcji w podziale na kategorie.

In [31]:
aggregate = allegro_transactions.groupby(by='main_category', as_index=False).agg({'price':['sum','count']}).sort_values(('price','sum'), ascending=False)
plt.figure(figsize=(16,16))
plt.style.use('seaborn')
plt.barh(aggregate.main_category, aggregate.loc[:,('price','sum')], label = 'price sum', color='c')
plt.ylabel('Category', fontdict={'size':16})
plt.xlabel('Category value in PLN', fontdict={'size':16})
plt.title('Category values in PLN', fontdict={'size':32})
plt.show()